# -*- coding: utf-8 -*-
# created on 2017/02/08

# 函数性质综合 - 自动推理

from operator import itemgetter
from sympy import sympify, Abs, solve, symbols, Eq
from mathsolver.functions.base import BaseFunction, BaseNumber, new_latex
from mathsolver.functions.hanshu.zhouqi import (hanshu_zhouqi_wrapper, is_faminusx, is_m_minus_faminusx, 
                                                is_minus_faminusx, has_f)
from mathsolver.functions.sympy_utils import get_all_child


def duicheng_infer_zhouqi(eq1, eq2):
    """hs108.函数对称性推导周期性
    :param eq1 Eq
    :param eq2 Eq"""
    eq1, eq2 = normalize_eq(eq1), normalize_eq(eq2)

    assert eq1.lhs == sympify('f(x)') and eq2.lhs == sympify('f(x)')
    eq1_rhs = eq1.rhs
    eq2_rhs = eq2.rhs

    # (1) 轴对称+轴对称
    if is_faminusx(eq1_rhs) is not None and is_faminusx(eq2_rhs) is not None:
        a = is_faminusx(eq1_rhs)
        b = is_faminusx(eq2_rhs)
        dt = Abs(a - b)
        steps = [["", "由 %s, %s 得到 %s = %s," % (new_latex(eq1), new_latex(eq2), eq1_rhs, eq2_rhs)],
                 ["", " 可知函数的最小正周期为 T = %s" % dt]]

    # (2) 中心对称+中心对称
    elif is_m_minus_faminusx(eq1_rhs) is not None and is_m_minus_faminusx(eq2_rhs) is not None:
        a = is_m_minus_faminusx(eq1_rhs)
        b = is_m_minus_faminusx(eq2_rhs)
        dt = Abs(a - b)
        steps = [
            ["", "由 %s, %s 得到 %s = %s, 得到 f(x) = f(x + %s - %s)，" % (new_latex(eq1), new_latex(eq2), eq1_rhs, eq2_rhs, a, b)],
            ["", "所以函数的最小正周期为 T = %s" % dt]]

    # (3) 轴对称 + 中心对称
    elif is_faminusx(eq1_rhs) is not None and is_minus_faminusx(eq2_rhs) is not None:
        a = is_faminusx(eq1_rhs)
        b = is_minus_faminusx(eq2_rhs)
        dt = 2 * Abs(a - b)
        steps = [["", "根据 %s, %s 得到 f(-x) = f(%s+x), f(-x) = -f(%s+x), 所以 f(%s+x) = -f(%s+x), "
                  % (new_latex(eq1), new_latex(eq2), a, b, a, b)],
                 ["", "即 f(x) = -f(x+%s-%s), 所以函数的最小正周期为 T = %s" % (2 * a, 2 * b, dt)]]

    elif is_faminusx(eq2_rhs) is not None and is_minus_faminusx(eq1_rhs) is not None:
        a = is_faminusx(eq2_rhs)
        b = is_minus_faminusx(eq1_rhs)
        dt = 2 * Abs(a - b)
        steps = [["", "根据 %s, %s 得到 f(-x) = f(%s+x), f(-x) = -f(%s+x), 所以 f(%s+x) = -f(%s+x), " %
                  (new_latex(eq1), new_latex(eq2), a, b, a, b)],
                 ["", "即 f(x) = -f(x+%s-%s), 所以函数的最小正周期为 T = %s" % (2 * a, 2 * b, dt)]]

    else:
        raise ValueError("根据 %s 和 %s 无法推导出周期性" % (new_latex(eq1), new_latex(eq2)))

    return dt, steps


def normalize_eq(eq):
    """把方程转换成 f(x) = ... 的格式
    :param eq: Eq"""
    eq_lhs, eq_rhs = eq
    expr = eq_lhs - eq_rhs
    fx = sympify("f(x)")
    x, y = symbols('x y')

    if eq_lhs == fx:
        return eq
    else:
        # 抽取出 f(*)
        fx_list = get_all_child(expr, has_f)
        # 如果含有 f(x)
        if fx in fx_list:
            eq_right = solve(expr, fx)[0]
        # 如果不含有 f(x)
        else:
            fgx = fx_list[0]
            gx = fgx.args[0]
            xx = solve(gx - y, x)[0]
            fx_map_fy = {item: item.subs(x, xx) for item in fx_list}
            transformed_expr = expr.subs(fx_map_fy).subs(y, x)
            eq_right = solve(transformed_expr, fx)[0]

        return Eq(fx, eq_right).args


def infer_zhouqi(*eqs):
    """根据等式推导周期
    :param eqs: an iterable of Eq"""
    assert len(eqs) < 3

    dt = []

    # 从单个等式推导
    for eq in eqs:
        try:
            zhouqi, steps = hanshu_zhouqi_wrapper(eq)
            dt.append([zhouqi, steps])
        except Exception:
            continue

    # 从两个等式推导
    try:
        zhouqi, steps = duicheng_infer_zhouqi(*eqs)
        dt.append([zhouqi, steps])
    except Exception:
        pass

    if dt:
        return min(dt, key=itemgetter(0))


def dairu_eq(expr, eq):
    """根据 eq 推导 expr 的表达式
    :param expr 表达式，例如 -f(*) 或者 f(*) 格式
    :param eq normalized eq, Eq(f(x), ...) 的格式"""

    # expr = f(*)
    if has_f(expr):
        f_arg = expr.args[0]
        return eq[1].subs(sympify('x'), f_arg)
    # expr = -f(*)
    elif has_f(-expr):
        f_arg = (-expr).args[0]
        return - eq[1].subs(sympify('x'), f_arg)
    else:
        raise ValueError('expr must be f(*) or -f(*), unknown expr type %s' % expr)


def get_f_arg(expr):
    """取出 expr 里面 f(arg) 中 arg 值
    :param expr 为只含有一个 f(a) 的表达式 实际应用中可能出现 f(*) 或者 -f(*)
    :return arg of f(arg)"""
    fx = get_all_child(expr, has_f)
    assert len(fx) == 1
    f_arg = fx[0].args[0]
    return f_arg


def parent_indices(i):
    """得到 index 为 i 的节点的所有母节点, 链表格式的树, root = 0 index"""
    indices = []

    while i != 0:
        parent = int((i - 1) / 2)
        indices.append(parent)
        i = parent

    return list(reversed(indices))


def parent_steps(infer_stack):
    """计算出推导步骤"""
    current_result_index = len(infer_stack)
    parents = parent_indices(current_result_index)
    return [infer_stack[i] for i in parents]


def infer_value(eqs, ios, q):
    """推导 f(q) 的值
    :param eqs: a iterable of normalized Eqs [f(x) = ... 的格式]
    :param ios: a dict of a -> b, where f(a) = b
    :param q: calculate f(q)"""

    # 如果 q 的值已知，直接输出 f(q)
    if q in ios:
        final_result = ios[q]
        step = "由题意知： f(%s) = %s" % (q, final_result)
        return final_result, step

    # 根据给定条件推导 f(q) 的值
    fq = sympify('f(%s)' % q)
    infer_stack = [fq]
    i = -1
    while i < 10:
        i += 1
        to_dairu_indices = range(2 ** i - 1, 2 * (2 ** i - 1) + 1, 1)
        to_dairu = [infer_stack[ii] for ii in to_dairu_indices]
        for f_item in to_dairu:
            for eq in eqs:
                intermediate_result = dairu_eq(f_item, eq)

                # 如果迭代得到的值已知，停止迭代，返回结果
                f_arg = get_f_arg(intermediate_result)
                if f_arg in ios:
                    # 最终结果
                    final_result = intermediate_result.subs(sympify('f(%s)' % f_arg), ios[f_arg])
                    # 推导步骤
                    all_intermediates = parent_steps(infer_stack)
                    all_intermediates.extend([intermediate_result, final_result])
                    step = ' = '.join(str(item) for item in all_intermediates)

                    return final_result, step
                else:
                    # 尝试解方程
                    solve_eq = solve(fq - intermediate_result, fq)
                    # 如果解出来则迭代结束，返回结果
                    if solve_eq and (solve_eq[0].is_Number or isinstance(solve_eq[0], int)):
                        final_result = solve_eq[0]
                        all_intermediates = parent_steps(infer_stack)
                        all_intermediates.append(intermediate_result)
                        step = ' = '.join(str(item) for item in all_intermediates) + "解方程得 %s = %s" % (
                            fq, final_result)

                        return final_result, step
                    else:
                        # 如果推导不出来结果，把中间值放到 stack 上面，继续推导
                        infer_stack.append(intermediate_result)

    if i == 10:
        raise ValueError('迭代超过十次、未找出问题解')


def approximate(a, q, zt):
    """周期为 T, q 逼近到 a
    :return 与 a 最近的值"""
    n = (q - a) // zt
    res = (q - a) % zt + a
    step = "根据周期为 %s 得 f(%s) = f(%s + %s * %s) =  f(%s)" % (
        new_latex(zt), new_latex(q), new_latex(res), new_latex(zt), new_latex(n), new_latex(res))
    return res, step


def approch_to_known_values(known_vals, q, zt):
    """根据求出来的周期，把 q 逼近到距离已知值最近的点
    :param known_vals a list of intergers i, f(i) 的值已知
    :param q 求 f(q)
    :param zt 周期"""

    # 根据 ios 把 q 逼近到已知值
    approximations = []
    for known_val in known_vals:
        approx, step = approximate(known_val, q, zt)
        approximations.append([approx, step])

    # 找出最优的逼近值
    if len(approximations) == 1:
        return approximations[0]
    else:
        return min(approximations, key=lambda item: min(Abs(item[0] - val) for val in known_vals))


def solve_hanshu_qiuzhi(eqs, ios, q):
    """函数性质综合求函数的值——函数值
    :param eqs: a iterable of Eqs
    :param ios: a dict of a -> b, where f(a) = b
    :param q: calculate f(q)"""

    # 把 eqs 都转换成 f(x) = ... 的形式
    eqs = [normalize_eq(item) for item in eqs]

    # 从 eqs 推导周期
    zt, zhouqi_steps = infer_zhouqi(*eqs)

    # 如果周期存在，根据周期值将 q 向已知值逼近
    if zhouqi_steps:
        q1, approx_step = approch_to_known_values(ios.values(), q, zt)
        zhouqi_steps.append(["", approx_step])
    else:
        q1 = q

    result, inference_step = infer_value(eqs, ios, q1)

    return result, zhouqi_steps + [["", inference_step]] + [["", "综上所述 f(%s) = %s" % (new_latex(q), new_latex(result))]]


class HanShuQiuZhi(BaseFunction):
    """065.函数性质综合求函数的值——函数值"""

    def solver(self, *args):
        eqs, io_values, funcname = args
        q = funcname.var
        result, steps = solve_hanshu_qiuzhi(eqs, io_values, q)
        self.steps.extend(steps)
        self.output.append(BaseNumber(result))

        self.label.add("函数性质综合求函数的值——函数值")
        return self


if __name__ == '__main__':
    pass
